Script VersionCheck
return "uac.qs 1.0.24";
//Add ShowG that show 2 layer of global data
//Added Log for CNode::Write
//1.0.2		Fixed but on OPC Call back for monitor
//1.0.3		Added WriteMulti under CHost
//1.0.4		Need qs 1.0.86 for the try catch feature
//1.0.7		Fixed typo on (Connnect).  Also no need to call lib if monitor list is empty
//1.0.10	fixed a bug on monitor
//1.0.11	Fix Compare problem
//1.0.15	CW 17 May 2022 Added log for OPC Read/Write/MultiRead/MultiWrite
//1.0.16	CW 28 May 2022 Change AppLogEx to AppLog
//1.0.17	CW 1 Jun 2022 if node is bad during startup, it will be removed from the Monitor List
//1.0.18	CW 2022 Jun 16 Added CV2 before making Lib call
//1.0.19	CW 2022 Jun 30 Fix QRead to be calling Read
//1.0.20	CW 2022 Jul 27 Reconnect fix if OPC fail
//1.0.21	CW 2022 Oct 04 Private_Connect should not returned until it is connected
//1.0.22	CW 2023 Mar 21 Subscription to auto auto subbscribe when reconnect
//1.0.23	CW 2023 Mar 28 Implemeneted SubIssue call back to recreate subscription
EndScript

Script CNode
(id, host, alias)

Object CurrentValue;
String ID, Host, Alias;
String Type;

ID = id;
Host = host;
Alias = alias;

EndScript

Script SubscriptionData
(node, cb, pi, lifetime, keepalive, priority)

Vector Node = node;
String CB = cb;
F8 PublishingInterval = pi;
U4 LifeTimeCount = lifetime;
U4 MaxKeepAliveCount = keepalive;
U4 Priority = priority;

EndScript

Script CHost
(name, config)

String ConnectString, CallBack, ThreadName;
String Name = name;
U0 seq = 0;
Queue Q;
Map MapNode;
Vector vWriteNode;
Map MapAliasToNodeID;
Map LocalAlias, Scheme, SchemeAlias;
Vector Subscription;
Map MapSubScription;			//key is subsID, data is the index to Subscription
Vector vMon;
Vector vOther;
UA_Client@uac UAC;
CV cv;
U4 SB = 0;
U4 ConnectStatus = 0;
I4 NameSpaceIndex;
U4 MaxInMulti;
String ConnectCallBack;

GCreateEx(StrAssign(ThreadName, Name, "_OPC_Thread"), "QThread");
Configure(config);

Script Configure
(config)

Reference p, p2, p3, p4;
Reference pMonitor, pMonitorNode, pGenerator;
U0 index, i;
String key;
Vector vkey, vvalue;
U0 extCount, loop;
String subcb;
U4 interval, priority, keepalive, lifetime;
Vector tmpv;

CallBack = ifexp(IFindEx(p, config, "CallBack"))(p)("OPC_CallBack");
	
if (IFindEx(p, config, "Connect"))
{
	ConnectString = p;
}
else
{
	AppLog("[Error] ", Name, " 'Connect' config is missing");
}

NameSpaceIndex = ifexp (IFindEx(p, config, "NameSpaceIndex")) (p) (2);
MaxInMulti = ifexp (IFindEx(p, config, "MaxInMulti")) (p) (0);
ConnectCallBack = ifexp (IFindEx(p, config, "ConnectCallBack")) (p) ("DefaultConnectCallBack");

AppLog("[", Name, "] MaxInMulti is set to ", MaxInMulti);
SetName@uac(UAC, Name, "CB_State", "CB_Inactive", "CB_SubsInactive");

PrintLn(Name, "start of config");
IFindEx(pGenerator, config, "Generator");
	
	if (IFindEx(pMonitor, config, "MonitorNode"))
	{
		foreach (pMonitorNode, pMonitor, index, key)
		{
			if (IIsEnd(pGenerator) == 0)
			{
				ReplaceGenerator(pGenerator, key, vkey);
				ReplaceGenerator(pGenerator, pMonitorNode, vvalue);
			}
			
			extCount = GetCount(vkey);
			if (extCount)
			{
				if (extCount == GetCount(vvalue))
				{
					loop = 0;
					while (loop < extCount)
					{
						VectorPush(vMon, vkey[loop]);
						MapAdd(MapAliasToNodeID, vkey[loop], vvalue[loop]);
						if (MapCheck(MapNode, vkey[loop]))
						{
							AppLog("[Error] ", Name, ", duplicate node name is not allowed, ", vkey[loop]);
							throw;
						}
						MapAddEx(MapNode, vkey[loop], "CNode", vvalue[loop], Name, vkey[loop]);				
						Inc(loop);
					}
				}
				else
				{
					AppLog("[Error] ", Name, ", alias count (", extCount, ") and id count (", GetCount(vvalue), ") do not match.  Orginal alias is ", key, " and original id is ", pMonitorNode);
					return -1;
				}
			}
			else
			{
				VectorPush(vMon, key);
				MapAdd(MapAliasToNodeID, key, pMonitorNode);
				if (MapCheck(MapNode, key))
				{
					AppLog("[Error] Duplicate node name is not allowed, ", key);
					return -1;
				}
				MapAddEx(MapNode, key, "CNode", pMonitorNode, Name, key);
			}
			VectorClear(vkey);
			VectorClear(vvalue);
		}
	}

	if (IFindEx(pMonitor, config, "Node"))
	{
		foreach(pMonitorNode, pMonitor, index, key)
		{
			if (IIsEnd(pGenerator) == 0)
			{
				ReplaceGenerator(pGenerator, key, vkey);
				ReplaceGenerator(pGenerator, pMonitorNode, vvalue);
			}
			
			extCount = GetCount(vkey);
			if (extCount)
			{
				if (extCount == GetCount(vvalue))
				{
					loop = 0;
					while (loop < extCount)
					{
						VectorPush(vOther, vkey[loop]);
						MapAdd(MapAliasToNodeID, vkey[loop], vvalue[loop]);
						if (MapCheck(MapNode, vkey[loop]))
						{
							AppLog("Duplicate node name is not allowed, ", vkey[loop]);
							throw;
						}
						MapAddEx(MapNode, vkey[loop], "CNode", vvalue[loop], Name, vkey[loop]);
						Inc(loop);
					}
				}
				else
				{
					AppLog("[Error] ", Name, ", alias count (", extCount, ") and id count (", GetCount(vvalue), ") do not match.  Orginal alias is ", key, " and original id is ", pMonitorNode);
					return -1;
				}
			}
			else
			{
				VectorPush(vOther, key);
				MapAdd(MapAliasToNodeID, key, pMonitorNode);
				if (MapCheck(MapNode, key))
				{
					AppLog("[Error] Duplicate node name is not allowed, ", key);
					return;
				}
				MapAddEx(MapNode, key, "CNode", pMonitorNode, Name, key);
			}
			
			VectorClear(vkey);
			VectorClear(vvalue);
		}
	}
	
if (IFindEx(pMonitor, config, "WriteNode"))
{
	foreach (pMonitorNode, pMonitor)
	{
		if (IIsEnd(pGenerator) == 0)
		{
			ReplaceGenerator(pGenerator, pMonitorNode, vkey);
			foreach (p, vkey)
			{
				VectorPush(vWriteNode, p);
			}
			VectorClear(vkey);
		}
		else
		{
			VectorPush(vWriteNode, pMonitorNode);
		}
	}
	AppLog("[", Name, "] vWriteNode is ", vWriteNode);
}
	
if (IFindEx(p, config, "LocalAlias"))				//if LocalAlias is defined
{
	foreach(p2, p, index, key)
	{
		if (IFind(p3, MapAliasToNodeID, p2))
		{
			MapAdd(MapAliasToNodeID, key, p3);
		}
		else
		{
			AppLog("[Error] ", key, " is defined in LocalAlias but ", p2, " is not defined at MapAliasToNodeID");
			return;
		}
	}
}
	
	
	if (IFindEx(p, config, "Subscription"))
	{
		foreach(p2, p)
		{
			if (IFindEx(p3, p2, "Node") == 0)
			{
				AppLog("[Error] Node is not configure for Subscription in ", Name);
				return;
			}
			foreach (p4, p3)
			{
				if (MapCheck(MapAliasToNodeID, p4) == 0)
				{
					AppLog("[Error] ", Name, " Alias ", p4, " is not defined");
					return;
				}
			}
			subcb = ifexp(IFindEx(p4, p2, "CallBack"))(p4)("DefaultSubsCallBack");
			interval = ifexp(IFindEx(p4, p2, "PublishingInterval"))(p4)(50);
			lifetime = ifexp(IFindEx(p4, p2, "LifeTimeCount"))(p4)(100000);
			keepalive = ifexp(IFindEx(p4, p2, "MaxKeepAliveCount"))(p4)(1000);
			priority = ifexp(IFindEx(p4, p2, "Priority"))(p4)(0);
			
			VectorPushEx(Subscription, "SubscriptionData", p3, subcb, interval, lifetime, keepalive, priority);
		}
		PrintLn(Subscription);
	}
	
	if (IFindEx(p, config, "Scheme"))
	{
		foreach(p2, p, index, key)
		{
			if (IFind(p3, Scheme, key))
			{
				AppLog("[Error] Scheme ", key, " is already defined");
				return;
			}
			
			MapAdd(SchemeAlias, key, p2);
			
			VectorClear(tmpv);
			foreach(p3, p2)
			{
				if (IFind(p4, MapAliasToNodeID, p3))
				{
					VectorPush(tmpv, p4);
					AppLog("[UAC] Scheme ", key, " added alias = ", p3, ", node = ", p4);
				}
				else
				{
					AppLog("[Error] Scheme ", key, " has undefined alias ", p3);
					return;
				}
			}
			MapAdd(Scheme, key, tmpv);
			AppLog("[", Name, "] scheme ", key, ", is for following node ", tmpv);
		}
	}
	
	Start();
	
	if (IFindEx(p, config, "MonitorPlan"))
	{
		foreach(p2, p, index, key)
		{
			PCall("Monitor", key, Name, p2["Interval"], p2["Plan"]);
		}
	}
	
	PrintLn(Name, " end of config");
	
EndScript

Script GetSB

CVLock(cv);
U4 tmp = Inc(SB);
CVUnlock(cv);
return tmp;

EndScript

Script QRead
(alias, value, ret)
Read(alias, value, ret);
EndScript

Script QWrite
(id, value, ret)
Write(id, value, ret);
EndScript

Script QReadMulti
(valias, value, ret)
VectorClear(value);
ReadMulti(valias, value, ret);
EndScript

Script QWriteMulti
(vID, vValue, ret)
WriteMulti(vID, vValue, ret);
EndScript

Script BrowseString
(id, ns, v, ret)
U4 t0 = GetTick();
U4 sb = GetSB();
Object o;
U4 rsb;

OPCLog(ScriptName(), " ", Name, " sb = ", sb, ", eid = ", QS_EID());
ChannelPost(Name, "BS", id, ns, sb, t0);

Again:
if (TaskGet(3000, o, ret, rsb))
{
	AppLog(ScriptName(), " Failed on ", Name, ", ", GetTick() - t0, ", sb = ", sb, ", eid = ", QS_EID());
	ret = -1;
}
else
{
	if (rsb != sb)
	{
		AppLog(ScriptName(), " ", Name, " Fatal, rsb = ", rsb, ", expected ", sb);
		goto Again;
	}
	if (ret == 0)
	{
		v = o;
	}
}

EndScript

Script BrowseNumber
(id, ns, v, ret)

U4 t0 = GetTick();
U4 sb = GetSB();
Object o;
U4 rsb;

OPCLog(ScriptName(), " ", Name, " sb = ", sb, ", eid = ", QS_EID());
ChannelPost(Name, "BN", id, ns, sb, t0);

Again:
if (TaskGet(3000, o, ret, rsb))
{
	AppLog(ScriptName(), " Failed on ", Name, ", ", GetTick() - t0, ", sb = ", sb, ", eid = ", QS_EID());
	ret = -1;
}
else
{
	if (rsb != sb)
	{
		AppLog(ScriptName(), " ", Name, " Fatal, rsb = ", rsb, ", expected ", sb);
		goto Again;
	}
	if (ret == 0)
	{
		v = o;
	}
}

EndScript

Script Read
(alias, value, ret)

U4 t0 = GetTick();
U4 sb = GetSB();
Object o;
U4 rsb;
Reference p;

if (IFind(p, MapAliasToNodeID, alias) == 0)
{
	AppLog("[Error] ", Name, " read failure, unknown alias ", alias);
	AppLog(StackInfo());
	ret = -1;
	return;
}

OPCLog(ScriptName(), " ", Name, " sb = ", sb, ", eid = ", QS_EID());
ChannelPost(Name, "Read", p, sb, t0);

Again:
if (TaskGet(3000, o, ret, rsb))
{
	AppLog(ScriptName(), " Failed on ", Name, ", ", GetTick() - t0, ", sb = ", sb, ", eid = ", QS_EID());
	ret = -1;
}
else
{
	if (rsb != sb)
	{
		AppLog(ScriptName(), " ", Name, " Fatal, rsb = ", rsb, ", expected ", sb);
		goto Again;
	}
	if (ret == 0)
	{
		value = o;
	}
}

EndScript

Script OPCTypeHelper
(alias, type)
Object o;
I4 ret;
Read(alias, o, ret);
if (ret == 0)
{
	MapNode[alias].Type = GetActualType(o);
	type = GetActualType(o);
}
return ret;
EndScript

Script Write
(alias, value, ret)

U4 t0 = GetTick();
U4 sb = GetSB();
U4 rsb;
Object o;

Reference p;

if (IFind(p, MapAliasToNodeID, alias) == 0)
{
	AppLog("[Error] ", Name, " Write failure, unknown alias ", alias);
	ret = -1;
	return;
}

String type = MapNode[alias].Type;

if (StrLen(type) == 0 && OPCTypeHelper(alias, type))
{
	AppLog("[Error] ", Name, " Write failure, alias ", alias, " type is not known");
	ret = -1;
	return;
}


switch (type)
{
case "Boolean"
case "U1"
case "U2"
case "U4"
case "U8"
case "I1"
case "I2"
case "I4"
case "I8"
case "F4"
case "F8"
case "String"
case "PBoolean"
case "PU1"
case "PU2"
case "PU4"
case "PU8"
case "PI1"
case "PI2"
case "PI4"
case "PI8"
case "PF4"
case "PF8"
case "PString"
	ObjCreateEx(o, type);
	o = value;
	break;
	
default
	AppLog("[Error] ", Name, " unsupported type ", type);
	ret = -1;
	return;
}

OPCLog(ScriptName(), " ", Name, " sb = ", sb, ", eid = ", QS_EID());
ChannelPost(Name, "Write", p, o, type, sb, t0);

Again:
if (TaskGet(3000, ret, rsb))
{
	AppLog(ScriptName(), " Failed Write on ", Name, ", ", GetTick() - t0, ", sb = ", sb, ", eid = ", QS_EID());
	ret = -1;
}
else
{
	if (rsb != sb)
	{
		AppLog(ScriptName(), " ", Name, " Fatal, rsb = ", rsb, ", expected", sb);
		goto Again;
	}
}

EndScript

Script ReadScheme
(scheme, vOut)

Reference p;

if (IFind(p, Scheme, scheme))
{
	return ReadMultiRaw(p, vOut);
}

AppLog("[Error] ", Name, " ReadScheme failure, ", scheme, " is not defined");
return -1;
EndScript

Script HelperWriteType


EndScript

Script HelperWriteMultiType
(vAlias, vType)
EndScript

Script WriteScheme
(scheme, vOut)

Reference p;
Object o;
U4 t0, sb, rsb;
U4 ret;

if (IFind(p, SchemeAlias, scheme))
{
	WriteMulti(p, vOut, ret);
	
	/*
	t0 = GetTick();
	sb = GetSB();

	OPCLog(ScriptName(), " ", Name, " sb = ", sb, ", eid = ", QS_EID());
	ChannelPost(Name, "ReadMulti", p, sb, t0);

	Again:
	if (TaskGet(3000, o, ret, rsb))
	{
		AppLog(ScriptName(), " Failed ReadMulti on ", Name, ", ", GetTick() - t0, ", sb = ", sb, ", eid = ", QS_EID());
		return -1;
	}

	if (rsb != sb)
	{
		AppLog(ScriptName(), " ", Name, " Fatal, rsb = ", rsb, ", ", sb);
		goto Again;
	}
	vOut = o;
	*/
	return ret;
}

AppLog("[Error] ", Name, " WriteScheme failure, ", scheme, " is not defined");
return -1;
EndScript

Script ReadMultiRaw
(vNode, vOut)

Object o;
U4 t0 = GetTick();
U4 sb = GetSB();
U4 rsb;
I4 ret = -1;

OPCLog(ScriptName(), " ", Name, " sb = ", sb, ", eid = ", QS_EID());
ChannelPost(Name, "ReadMulti", vNode, sb, t0);

Again:
if (TaskGet(3000, o, ret, rsb))
{
	AppLog("[Error] ", Name, " ReadMulti failed, elapse time = ", GetTick() - t0, ", sb = ", sb, ", eid = ", QS_EID());
	return -1;
}

if (rsb != sb)
{
	AppLog(ScriptName(), " ", Name, " Fatal, rsb = ", rsb, ", expected ", sb);
	goto Again;
}
vOut = o;
	
return ret;

EndScript

Script ReadMulti
(vAlias, vOut, ret)

U0 i;
Reference p, p2;
Vector vNode;

if (MaxInMulti && MaxInMulti < GetCount(vAlias))
{
	AppLog("[Error] ", Name, " WriteMulti failed because node count ", GetCount(vAlias), ", is more that MaxInMulti, ", MaxInMulti);
	ret = -1;
	return;
}

foreach (p, vAlias, i)
{
	if (IFind(p2, MapAliasToNodeID, p))
	{
		VectorPush(vNode, p2);
	}
	else
	{
		AppLog("MultiRead failure, unknown alias ", p);
		ret = -1;
		return;
	}
}

ret = ReadMultiRaw(vNode, vOut);

EndScript

Script WriteMultiRaw
(vNode, vType, vValue)

U4 t0 = GetTick();
U4 sb = GetSB();
U4 rsb;
I4 ret;

OPCLog(ScriptName(), " ", Name, " sb = ", sb, ", eid = ", QS_EID());
ChannelPost(Name, "WriteMulti", vNode, vValue, vType, sb, t0);

Again:
if (TaskGet(3000, ret, rsb))
{
	AppLog("[Error] ", Name, " WriteMultiRaw failed, time elapsed = ", GetTick() - t0, ", sb = ", sb, ", eid = ", QS_EID());
	return -1;
}

if (rsb != sb)
{
	AppLog(ScriptName(), " ", Name, " Fatal, rsb = ", rsb, ", expected ", sb);
	goto Again;
}

return ret;

EndScript

Script WriteMulti
(vAlias, vOut, ret)

Vector vNode, vType, vValue;
String type, tmp;
Reference p, p2;
U0 i;

if (MaxInMulti && MaxInMulti < GetCount(vAlias))
{
	AppLog("[Error] ", Name, " WriteMulti failed because node count ", GetCount(vAlias), ", is more that MaxInMulti, ", MaxInMulti);
	ret = -1;
	return;
}

foreach(p, vAlias, i)
{
	if (IFind(p2, MapAliasToNodeID, p) == 0)
	{
		AppLog("[Error] ", Name, " WriteMulti failure, unknown alias ", p);
		AppLog(StackInfo());
		ret = -1;
		return;
	}
	
	VectorPush(vNode, p2);
	type = MapNode[p].Type;
	
	if (StrLen(type) == 0 && OPCTypeHelper(p, type))
	{
		AppLog("[Error] ", Name, " Write failure, alias ", p, " type is not known");
		ret = -1;
		return;
	}

	switch (type)
	{
	case "Boolean"
	case "U1"
	case "U2"
	case "U4"
	case "U8"
	case "I1"
	case "I2"
	case "I4"
	case "I8"
	case "F4"
	case "F8"
	case "String"
	case "PBoolean"
	case "PU1"
	case "PU2"
	case "PU4"
	case "PU8"
	case "PI1"
	case "PI2"
	case "PI4"
	case "PI8"
	case "PF4"
	case "PF8"
	case "PString"
		VectorPush(vType, type);
		VectorPushEx(vValue, type);
		VectorBack(vValue) = vOut[i];
		break;
		
	default
		AppLog("[Error] ", Name, " unsupported type ", type);
		ret = -1;
		return;
	}
}

ret = WriteMultiRaw(vNode, vType, vValue);
EndScript

Script BN
(id, ns, o, ret)
Vector v;
I4 lret;
U4 ID = id;
U4 NS = ns;

BrowseByNumber@uac(UAC, NS, ID, v, lret);
ret = lret;
if (ret == 0)
{
	o = v;
}

EndScript

Script BS
(id, ns, o, ret)
Vector v;
I4 lret;
String ID = id;
U4 NS = ns;
if (ConnectStatus)
{
	BrowseByString@uac(UAC, NS, ID, v, lret);
	ret = lret;
	if (ret == 0)
	{
		o = v;
	}
}
else
{
	ret = -1;
}

EndScript

Script READ
(node, value, ret)

ret = -1;
Object v;

OPCLog("Read from (", node, ") start");
if (ConnectStatus)
{
	ReadData@uac(UAC, node, v, ret);
}
else
{
	ret = -1;
}

if (ret)
{
	OPCLog("Read failure on (", node, "), returned ", ret);
}
else
{
	value = v;
	OPCLog("Read from (", node, ").  Value is ", v);
}
EndScript

Script READMULTI
(vNode, vOut, ret, sb)

OPCLog("MultiRead ", Inc(seq), ", sb = ", sb, " start\x0A", vNode);
if (ConnectStatus)
{
	ReadMultiData@uac(UAC, vNode, vOut, ret);
}
else
{
	AppLog("MultiRead ", Inc(seq), ", sb = ", sb, " failed because ", Name, " is not connected");
	OPCLog("MultiRead ", Inc(seq), ", sb = ", sb, " failed because ", Name, " is not connected");
	ret = -1;
	return;
}

if (ret)
{
	OPCLog(Name, " sb = ", sb, " ReadMultiData failed, ", ret);
}
else
{
	OPCLog(Name, " sb = ", sb, " ReadMultiData returned ", ret, "\x0A", vOut);
}
EndScript

Script WRITE
(node, value, type, ret)

if (ConnectStatus)
{
	OPCLog("Write ", value, " to ", node, "(", type, ") start");

	switch (type)
	{
	case "Boolean"
	case "U1"
	case "U2"
	case "U4"
	case "U8"
	case "I1"
	case "I2"
	case "I4"
	case "I8"
	case "F4"
	case "F8"
		WriteData@uac(UAC, node, type, value, ret);
		break;

	case "PBoolean"
	case "PI1"
	case "PI2"
	case "PI4"
	case "PI8"
	case "PU1"
	case "PU2"
	case "PU4"
	case "PU8"
	case "PF4"
	case "PF8"
	case "PString"
		WriteDataEx@uac(UAC, node, type, GetCount(value), value, ret);
		break;
		
	case "String"
		WriteData@uac(UAC, node, type, ToStream(value), ret);
		break;
		
	default
		AppLog(ScriptName(), " - unknown data type ", type);
		ret = -1;
		break;
	}
	OPCLog("Write ", value, " to ", node, "(", type, ") returned ", ret);

}
else
{
	ret = -1;
}
EndScript

Script WRITEMULTI
(vNode, vOut, vType, ret)

String str;
Reference p;
U0 i;

if (ConnectStatus)
{
	StrAppend(str, " WriteMulti\x0A");
	foreach(p, vNode, i)
	{
		StrAppend(str, p, ", ", vOut[i], ", ", vType[i], "\x0A");
	}

	OPCLog(str);

	WriteMultiData@uac(UAC, vNode, vType, vOut, ret);
	if (ret)
	{
		OPCLog(Name, " WriteMulti failed, returned ", ret);
	}
	else
	{
		OPCLog(Name, " WriteMulti success");
	}
}
else
{
	AppLog("[Error] ", Name, " WriteMulti is rejected because OPC is not connected, ", ConnectStatus);
	ret = -1;
}

EndScript

Script Private_Connect
(skipCCB)

U4 ret;
U0 eid;
Object obj;
I4 SecureChannelState, SessionState, Status;
U4 WaitCount;

Again:
AppLog("Making OPC connection for ", Name);
Connect@uac(UAC, ConnectString, ret);
if (ret)
{
	AppLog("[Error] Failed to make OPC connection for ", Name, ", code = ", ret);
	Sleep(1000);
	goto Again;
}

GetAllState@uac(UAC, SecureChannelState, SessionState, Status, ret);
if (ret)
{
	AppLog("[", Name, "] Failed to GetAllState");
	Sleep(1000);
	goto Again;
}

AppLog(Name, " connect call is successful.  SecureChannelState = ", SecureChannelState, ", SessionState = ", SessionState, ", Status = ", Status);

WaitAgain:

if (SecureChannelState != 6 || SessionState != 4)
{
	AppLog("[", Name, "] UA connected but SecureChannelState is incorrect");
	IterateEx@uac(UAC, 1000, ret);
	if (ret)
	{
		AppLog("[Private_Connect] IterateEx retuned ", ret);
		ConnectStatus = 0;
		Sleep(1000);
		goto Again;
	}
	
	GetAllState@uac(UAC, SecureChannelState, SessionState, Status, ret);
	if (ret)
	{
		AppLog("[", Name, "] Failed to GetAllState");
		ConnectStatus = 0;
		Sleep(1000);
		goto Again;
	}
	
	if (Status)
	{
		AppLog("[", Name, "] Status = ", Status, " in Private_Connect");
		Sleep(1000);
		goto Again;
	}
		
	goto WaitAgain;
}

ConnectStatus = 1;
AppLog("[", ScriptName(), "] Calling MakeSub for ", Name);
MakeSub();

if (GetCount(Parameter) == 0 || skipCCB == 0)
{
	AppLog("[", ScriptName(), "] Calling connect callback ", ConnectCallBack, " for ", Name);
	ThreadPost(:ThreadName, ConnectCallBack, Name);
}

EndScript

Script Start
PCall("MessageLoop");
EndScript

Script MultiReadHelper
(context, iteration, max, tmpvIn, vOut)
U4 ret;
U4 n = GetCount(tmpvIn);
Vector tmpvOut;
U4 loop = 0;
U0 u0 = 0;

OPCLog("[", Name, "] ", context, " ", Inc(seq), " start ", iteration, "/", max, "\x0A", tmpvIn);
ReadMultiData@uac(UAC, tmpvIn, tmpvOut, ret);
OPCLog("[", Name, "] ", context, " ", seq, " returned ", ret, "\x0A", tmpvOut);

while (loop < n)
{
	VectorPush(vOut, tmpvOut[loop]);
	Inc(loop);
}

return ret;

EndScript 

Script Split_vvIn
(vIn, vvIn)

U4 N = GetCount(vIn);
U4 remain = N % MaxInMulti;
U4 n = N / MaxInMulti;
U4 loop = 0, loop2;

VectorClear(vvIn);
	
	while (loop < n)
	{
		VectorPushEx(vvIn, "Vector");
		loop2 = 0;
		while (loop2 < MaxInMulti)
		{
			VectorPush(vvIn[loop], vIn[loop * MaxInMulti + loop2]);
			Inc(loop2);
		}
		
		Inc(loop);
	}
	
	if (remain)
	{
		VectorPushEx(vvIn, "Vector");
		loop2 = 0;
		while (loop2 < remain)
		{
			VectorPush(vvIn[loop], vIn[loop * MaxInMulti + loop2]);
			Inc(loop2);
		}
	}

EndScript 


Script MessageLoop

U4 ReconnectFlag;
U4 Count = 0;
Object value;
U4 ret;
U0 p, size;
U4 t0, t1;		//to control the sampling

U4 loop, n, N;
U4 loop2;
U0 ptr;

String type;
Vector vIn, vIn2, vOut;		//vIn2 is a 
Vector vTemp;
Reference pRef;
Object obj;
U0 eid;
U4 T0, T1;
U4 remain;
U0 u0 = 0;
I4 SecureChannelState, SessionState, Status;
Vector tmpvIn, tmpvOut;

Vector vvIn;

QS_SetDescription(Name);
AppLog("Starting MessageLoop for ", Name);

SetNameSpace@uac(UAC, NameSpaceIndex, ret);

n = GetCount(vWriteNode);
loop = 0;

foreach (pRef, vWriteNode)
{
	VectorPush(vIn, MapAliasToNodeID[vMon[loop]]);
}

Private_Connect(1);			//in this call, skip the ConnectCallBack

N = GetCount(vIn);
AppLog("[", Name, "] Total of ", N, " node is configured");

if (N)
{
	if (MaxInMulti && MaxInMulti < N)
	{
		Split_vvIn(vIn, vvIn);
		
		n = GetCount(vvIn);
		AppLog("[", Name, "] MultiRead/MultiWrite is split into ", n, " parts");
		AppLog(vvIn);
		
		loop = 0;
		while (loop < n)
		{
			ret = MultiReadHelper("First MultiRead", loop + 1, n, vvIn[loop], vOut);
			if (ret)
			{
				AppLog("[", Name, "] MultiReadHelper failed");
			}
			Inc(loop);
		}
	}
	else
	{
		OPCLog("[", Name, "] First MultiRead ", Inc(seq), " start\x0A", vIn);
		ReadMultiData@uac(UAC, vIn, vOut, ret);
		OPCLog("[", Name, "] First MultiRead ", seq, " returned ", ret, "\x0A", vOut);
	}

	AppLog("[", Name, "] First MultiRead Node\x0A", vIn, "\x0A[",
				Name, "] First MultiRead data\x0A", vOut);
}

loop = 0;
while (loop < N)
{	
	if (StrCmp(GetType(vOut[loop]), "U0"))
	{
		MapNode[vWriteNode[loop]].Type = GetType(vOut[loop]);
		MapNode[vWriteNode[loop]].CurrentValue = vOut[loop];
	}
	else
	{
		AppLog("[Error] ", vWriteNode[loop], " is bad");
	}
	Inc(loop);
}

//put back vIn as those required in monitor 
VectorClear(vIn);
n = GetCount(vMon);
loop = 0;
while (loop < n)
{
	VectorPush(vIn, MapAliasToNodeID[vMon[loop]]);
	Inc(loop);
}

if (MaxInMulti && MaxInMulti < N)
{
	Split_vvIn(vIn, vvIn);
}

AppLog(Name, " is up");
AppLog("[", ScriptName(), "] Calling connect callback ", ConnectCallBack, " for ", Name);
ThreadPost(:ThreadName, ConnectCallBack, Name);

AAA:

if (ConnectStatus == 0)
{
	Private_Connect();
}

while (ChannelGetEx(Name, 100, eid, obj) == 0)			//if time out (0 means there are request)
{
	//handled the command
	HandleCmd(eid, obj, ReconnectFlag);
	if (ReconnectFlag)
	{
		AppLog("[Error] ReconnectFlag requesting for reconnect");
		ConnectStatus = 0;
	}
}


T1 = GetTick();
IterateEx@uac(UAC, 0, ret);
T0 = GetTick();
if (T0 - T1 > 800)
{
	AppLog("[Error] Iterate time took ", T0 - T1, " ms");
}

if (ret)
{
	AppLog("[", Name, "] Iterate return code ", ret);
	ConnectStatus = 0;
	goto AAA;
}

t1 = GetTick();
if (t1 - t0 > 500)
{
	t0 = t1;
	
	VectorClear(vOut);

	if (GetCount(vIn))
	{
		n = GetCount(vvIn);
		if (n)
		{
			loop = 0;
			while (loop < n)
			{
				ret = MultiReadHelper("Monitor MultiRead", loop + 1, n, vvIn[loop], vOut);
				if (ret)
				{
					AppLog("[", Name, "] MultiReadHelper failed in monitor ", loop + 1, "/", n);
				}
				Inc(loop);
			}
		}
		else
		{
			OPCLog("Monitor MultiRead ", Inc(seq), " start\x0A", vIn);
			ReadMultiData@uac(UAC, vIn, vOut, ret);
			OPCLog("Monitor MultiRead ", seq, " returned ", ret, "\x0A", vOut);
		}

		if (ret)
		{
			AppLog("[Error] Monitor MultiRead failed, ", ret);
			if (ret != -1)
			{
				ConnectStatus = 0;
			}
		}
		else
		{
			n = GetCount(vOut);
			loop = 0;
			while (loop < n)
			{
				if (GetActualType(vOut[loop]) == "U0")
				{
					OPCLog("Bad node encountered, ", vMon[loop], ", ", vIn[loop]);
					AppLog("Bad node encountered, ", vMon[loop], ", ", vIn[loop]);
				}
				else
				{
					if (GetActualType(MapNode[vMon[loop]].CurrentValue) == "EOF" || Compare(vOut[loop], MapNode[vMon[loop]].CurrentValue, vMon[loop], MapNode[vMon[loop]]))
					{
						GLogLn("MonitorLog", ",", MapAliasToNodeID[vMon[loop]], ",", vMon[loop], ",", vOut[loop], ",", GetActualType(vOut[loop]));
						MapNode[vMon[loop]].CurrentValue = vOut[loop];
						ThreadPost(:ThreadName, CallBack, vMon[loop], vOut[loop], Name);		//making call back
					}
				}
				Inc(loop);
			}
		}
	}
}
goto AAA;

EndScript

Script MakeSub

Reference pSub, pNode;
U4 sub;
I4 ret;
U0 i;
U4 monID;
AppLog("Configure subscription for ", Name, "\x0A", Subscription);
foreach(pSub, Subscription, i)
{
	if (GetCount(pSub.Node))
	{
		CreateSubscription@uac(UAC, sub, pSub.CB, Name, pSub.PublishingInterval, pSub.LifeTimeCount, pSub.MaxKeepAliveCount, 0, 1, pSub.Priority, ret);
		if (ret)
		{
			AppLog("[Error] Failed to CreateSubscription");
		}
		else
		{
			AppLog("[Subscription] Created ", sub, " for ", Name);
			MapAdd(MapSubScription, sub , pSub.Node);
			
			foreach (pNode, pSub.Node)
			{
				AddMonitoredItem@uac(UAC, sub, MapAliasToNodeID[pNode], pNode, monID, ret);
				if (ret)
				{
					AppLog("[Error] Failed to AddMonitoredItem ", pNode, " to sub id ", sub, " at ", Name);
				}
				else
				{
					AppLog("[AddMonitoredItem] Added ", pNode, " to sub id ", sub, " at ", Name, ", monID = ", monID);
				}
			}
		}
	}
}
EndScript

Script DeleteSubscription
(id)
U4 sub_id = id;
CVLock(cv);
RemoveSubscription@uac(UAC, sub_id);
CVUnlock(cv);
EndScript

Script DeleteMonitoredItem
(id, item)
U4 sub_id = id;
U4 mon_id = item;
CVLock(cv);
RemoveMonitoredItem@uac(UAC, sub_id, mon_id);
CVUnlock(cv);
EndScript

Script CheckOPCStatus

I4 SecureChannelState, SessionState, Status;
I4 ret;

GetAllState@uac(UAC, SecureChannelState, SessionState, Status, ret);
if (ret || Status || SecureChannelState != 6 || SessionState != 4)
{
	AppLog("[", Name, "] Detected failure.  SecureChannelState=", SecureChannelState, ", SessionState=", SessionState, ", Status=", Status, ", ret = ", ret);
	ConnectStatus = 0;
	Private_Connect();
}
EndScript

Script HandleCmd
(eid, v, rflag)
rflag = 0;
I4 ret = -1;
Object value;
Vector vOut;
U0 systembyte;

switch (v[0])
{
case "BS"
	//ChannelPost(Name, "BS", id, ns, sb, t0);
	systembyte = v[3];
	OPCLog(Name, " started ", v[0], ", id = ", v[1], ", ns = ", v[2], ", sb = ", systembyte, ", eid = ", eid);
	if (GetTick() - v[4] < 2800)
	{
		BS(v[1], v[2], value, ret);
		if (GetTick() - v[4] > 3000)
		{
			goto PostLabel;
		}

		OPCLog(Name, " completed ", v[0], ", sb = ", systembyte, ", eid = ", eid, ", ret = ", ret);
		TaskPost(eid, value, ret, systembyte);			// posting the reply
		return ret;
	}
	goto PreLabel;
	
case "BN"
	//ChannelPost(Name, "BN", id, ns, sb, t0);
	systembyte = v[3];
	OPCLog(Name, " started ", v[0], ", id = ", v[1], ", ns = ", v[2], ", sb = ", systembyte, ", eid = ", eid);
	if (GetTick() - v[4] < 2800)
	{
		BN(v[1], v[2], value, ret);
		if (GetTick() - v[4] > 3000)
		{
			goto PostLabel;
		}
		
		OPCLog(Name, " completed ", v[0], ", sb = ", systembyte, ", eid = ", eid, ", ret = ", ret);
		TaskPost(eid, value, ret, systembyte);
		return ret;
	}
	
	goto PreLabel;
	
case "Read"
	systembyte = v[2];
	OPCLog(Name, " started ", v[0], ", sb = ", systembyte, ", eid = ", eid);
	if (GetTick() - v[3] < 2800)
	{
		READ(v[1], value, ret);
		
		switch (ret)
		{
		case 0
		case -1                 
		case -2144141312						//UA_STATUSCODE_BADNODEIDINVALID
		case -2144075776						//UA_STATUSCODE_BADNODEIDUNKNOWN
		case -2147483648						//UA_STATUSCODE_BADTCPMESSAGETOOLARGE
			break;

		default
			AppLog("[Open62541] READ returns ", ret);
			rflag = 1;
		}
		if (GetTick() - v[3] > 3000)
		{
			goto PostLabel;
		}
		
		OPCLog(Name, " completed ", v[0], ", sb = ", systembyte, ", eid = ", eid, ", ret = ", ret);
		TaskPost(eid, value, ret, systembyte);
		return ret;
	}
	
	goto PreLabel;
	
case "ReadMulti"
	systembyte = v[2];
	OPCLog(Name, " started ", v[0], ", sb = ", systembyte, ", eid = ", eid);
	if (GetTick() - v[3] < 2800)
	{
		READMULTI(v[1], vOut, ret, systembyte);
		switch (ret)
		{
		case 0
		case -1
			break;
			
		default
			AppLog("[Open62541] READMULTI returns ", ret);
			rflag = 1;
		}

		if (GetTick() - v[3] > 3000)
		{
			goto PostLabel;
		}
		OPCLog(Name, " completed ", v[0], ", sb = ", systembyte, ", eid = ", eid, ", ret = ", ret);
		TaskPost(eid, vOut, ret, systembyte);
		return ret;
	}
	goto PreLabel;
	
case "Write"
	systembyte = v[4];
	OPCLog(Name, " started ", v[0], ", sb = ", systembyte, ", eid = ", eid);
	if (GetTick() - v[5] < 2800)
	{
		WRITE(v[1], v[2], v[3], ret);
		if (GetTick() - v[5] > 3000)
		{
			goto PostLabel;
		}
		
		OPCLog(Name, " completed ", v[0], ", sb = ", v[4], ", eid = ", eid, ", ret = ", ret);
		TaskPost(eid, ret, systembyte);
		return ret;
	}
	goto PreLabel;
	
case "WriteMulti"
	systembyte = v[4];
	OPCLog(Name, " started ", v[0], ", sb = ", systembyte, ", eid = ", eid);
	if (GetTick() - v[5] < 2800)
	{
		WRITEMULTI(v[1], v[2], v[3], ret);
		if (GetTick() - v[5] > 3000)
		{
			goto PostLabel;
		}
		OPCLog(Name, " completed ", v[0], ", sb = ", systembyte, ", eid = ", eid, ", ret = ", ret);
		TaskPost(eid, ret, systembyte);
		return ret;
	}
	goto PreLabel;
	
default
	AppLog(Name, ": Unhandled request in ", ScriptName(), ", ", v[0]);
	AppLog(v);
	//fall through
}

return ret;

PreLabel:
AppLog("[", Name, "] OPC Pre Timeout] ", v[0], ", sb = ", systembyte);
OPCLog("[", Name, "] OPC Pre Timeout] ", v[0], ", sb = ", systembyte);
return ret;

PostLabel:
AppLog("[", Name, "] OPC Post Timeout] ", v[0], ", sb = ", systembyte);
OPCLog("[", Name, "] OPC Post Timeout] ", v[0], ", sb = ", systembyte);
return ret;

EndScript

Script Compare
(x, y, node_name, node)

//return 1 means need to CallBack

U4 loop = 0, count;
count = GetCount(x);
if (count != GetCount(y))
{
	AppLog("[Error] Count failure for x, ", GetCount(x), " and y, ", GetCount(y), ", node = ", node_name, "\x0A", node);
	return 0;
}

if (GetActualType(x) == "Vector" && GetActualType(y) == "Vector")
{
	if (VectorComparison(x, y))
	{
		AppLog("Vector HIT\x0A", x, "\x0A", y, "\x0A\x0A");
		return 1;
	}
	else
	{
		AppLog("Vector Skip\x0A", x, "\x0A", y, "\x0A\x0A");
		return 0;
	}
}

	if (GetActualType(x) == "U0")
	{
		AppLog("[Error] Type failure for x, ", GetActualType(x), " for ", Name);
		return 0;
	}

	if (count == 1)
	{
		if (x == y)
		{
			return 0;
		}
		return 1;
	}
	else
	{
		while (loop < count)
		{
			if (x[loop] != y[loop])
			{
				return 1;
			}
			Inc(loop);
		}
		return 0;
	}

EndScript

Script VectorComparison
(x, y)
U0 i;
Reference p;
U0 n, loop = 0;
foreach (p, x, i)
{
	if (GetActualType(p) == "Vector" && GetAcutalType(y[i]) == "Vector")
	{
		if (VectorComparison(p, y[i]))
		{
			return 1;
		}
	}
	
	n = GetCount(p);
	if (n != GetCount(y[i]))
	{
		return 1;
	}
	
	while (loop < n)
	{
		if (x[loop] != y[loop])
		{
			return 1;
		}
		Inc(loop);
	}
}

return 0;

EndScript

EndScript

Script ConfigureOPC

String name;
U4 ret, sub;
U4 loop2, n2, loop3;
U0 i;
Object opc;
U4 max;

Reference pOPC;


if (GetJSONFile("opc.json", opc))
{
	AppLog("[Error] Failed to read 'opc.json'");
	return;
}

foreach (pOPC, opc, i, name)
{
	if (MapCheck(Global, name))
	{
		AppLog("[Error] ", name ," is already defined in Global space");
	}
	else
	{
		GCreateEx(name, "CHost", name, pOPC);
	}
}

EndScript 

Script DefaultConnectCallBack
(host)
AppLog("[DefaultConnectCallBack] ", host);
EndScript

Script SubIssue
(name)
AppLog("[SubIssue] ", ScriptName(), "\x0A", Parameter);
:name.MakeSub();
EndScript

Script CB_UAC_DeleteSubscription
AppLog("[CB_UAC_DeleteSubscription] ", Parameter);
EndScript

Script CB_UAC_DeleteMonitorItem
AppLog("[CB_UAC_DeleteMonitorItem] ", Parameter);
EndScript

Script CB_State
(name, status, session, channel)
AppLog("[CB_State] ", name, " status = ", status, ", session = ", session, ", channel = ", channel);
if (:name.ConnectStatus)
{
	if (status)
	{
		:name.ConnectStatus = 0;
		AppLog("[", name, "] Detected disconnect at ", ScriptName(), "\x0A", Parameter);
	}
}
EndScript 

Script CB_Inactive
(host)

AppLog("[CB] ", ScriptName(), " ", host);
ThreadPostEx(Global[:host.ThreadName], "Default_Inactive_Handler", Parameter);		//making call back

EndScript

Script Default_Inactive_Handler
(host)
Reference p;
Object o;
String key;
I4 ret;

if (IBegin(p, :host.MapAliasToNodeID, key))
{
	:host.Read(key, o, ret);
	if (ret)
	{
		AppLog("[Error] Default_Inactive_Handler failed to perform Read for ", key, " at ", host);
	}
	else
	{
		AppLog("[CB] Default_Inactive_Handler read alias ", key, " succesfully for ", host, ", value = ", o);
	}
}
else
{
	AppLog("[Error] Error at Default_Inactive_Handler, no nodes configured for ", host);
}

EndScript

Script CB_SubsInactive
AppLog("[CB] ", ScriptName(), "\x0A", Parameter);
EndScript

Script ReplaceGenerator
(j, s, v)

U4 loop = 0, n;
n = GetCount(j);
String key, value;
String scopy;
Vector vCopy;
U4 lo, hi;
scopy = s;			//make a copy of the target string
U4 n2, loop2;
U0 xx;

while (loop < n)
{
	key = IMapGetKey(j, loop);
	if (GetActualType(j[loop]) == "String")
	{
		StrReplace(scopy, key, j[loop]);
	}
	Inc(loop);
}
	
VectorPush(v, scopy);

loop = 0;
while (loop < n)
{
	key = IMapGetKey(j, loop);
	if (GetActualType(j[loop]) == "Vector" && StrFind(v[0], key, 0, xx))
	{
		if (GetCount(j[loop]) == 2)
		{
			vCopy = v;
			VectorClear(v);
			
			lo = j[loop][0];
			hi = j[loop][1];
			
			if (lo <= hi)
			{
				while (lo <= hi)
				{
					n2 = GetCount(vCopy);
					loop2 = 0;
					while (loop2 < n2)
					{
						scopy = vCopy[loop2];
						StrReplace(scopy, key, lo);
						VectorPush(v, scopy);
						Inc(loop2);
					}
					Inc(lo);
				}
			}
			else
			{
				while (lo >= hi)
				{
					n2 = GetCount(vCopy);
					loop2 = 0;
					while (loop2 < n2)
					{
						scopy = vCopy[loop2];
						StrReplace(scopy, key, lo);
						VectorPush(v, scopy);
						Inc(loop2);
					}
					Dec(lo);
				}
			}
		}
		else
		{
			AppLog("[Error] Range config should only be lo and hi item", j[loop]);
			throw;
		}
	}
	
	
	Inc(loop);
}

EndScript

Script Monitor
(plan_name, host, interval, imap)

AppLog("Monitor started for ", plan_name, " under host ", host, " at interval of ", interval, "ms");

PVector pvec;
Vector vcb, vValue, vParam, vNode;

Reference p;
String key;
String ThreadName;
U0 index;

StrAssign(ThreadName, host, "_OPC_Thread");

Redim(pvec, GetCount(imap));
U0 loop, count;
I4 ret;

foreach(p, imap, index, key)
{
	VectorPush(vNode, key);
	VectorPush(pvec[index], key);
	VectorPushEx(pvec[index], "Object");
	VectorPush(pvec[index], host);
	
	VectorPush(vcb, p[0]);		//callback
	count = GetCount(p);
	loop = 1;
	while (loop < count)
	{
		VectorPush(pvec[index], p[loop]);
		Inc(loop);
	}
}

AppLog("[Monitor] vNode", vNode);
AppLog("[Monitor] pvec", pvec);
AppLog("[Monitor] vcb", vcb);

Again:
:host.ReadMulti(vNode, vValue, ret);
if (ret)
{
	AppLog("[Error] ReadMulti failed for ", host, " at plan ", plan_name);
}
else
{
	foreach (p, vValue, index)
	{
		if (GetActualType(pvec[index][1]) == "EOF" || GetActualType(pvec[index][1]) == "U0")
		{
			if (GetActualType(p) != "U0")
			{
				pvec[index][1] = p;
				//FCallEx(vcb[index], pvec[index]);
				ThreadPostEx(:ThreadName, vcb[index], pvec[index]);		//making call back
			}
			else
			{
				AppLog("[Error] Bad node encountered on ", vNode[index]);
			}
		}
		else
		{
			 if (GetActualType(p) != "U0" && CompareHelper(pvec[index][1], p))
			 {
				pvec[index][1] = p;
				GLogLn("MonitorLog", ",," , vNode[index], ",", p, ",", GetActualType(p));
				//FCallEx(vcb[index], pvec[index]);
				ThreadPostEx(:ThreadName, vcb[index], pvec[index]);		//making call back
			 }
		}
	}
}
Sleep(interval);
goto Again;

EndScript


Script CompareHelper
(a, b)

Reference p;
U0 i;

if (GetCount(a) != GetCount(b))
{
	return 1;
}

if (GetCount(a) == 1)
{
	return ifexp(a != b)(1)(0);
}

foreach (p, a, i)
{
	if (p != b[i])
	{
		return 1;
	}
}

return 0;

EndScript

Script BString
(name, ns, tag_name)
Vector obj;
U4 NS;
NS = ns;
BrowseByString@uac(:name, NS, tag_name, obj);
if (StrCmp(GetActualType(obj), "Vector") == 0)
{
	PrintLn(GetCount(obj));
	Display(obj);
}
EndScript

Script BNumber
(name, ns, id, ret)

Vector obj;
U4 ID, NS;
ID = id;
NS = ns;

ret = 0;		//to do
BrowseByNumber@uac(:name, NS, ID, obj);
//if (StrCmp(GetActualType(obj), "Vector") == 0)
//{
	PrintLn(GetCount(obj));
	Display(obj);
//}
EndScript

Script Display
(v)
String tmp;
U4 loop, n;
n = GetCount(v) / 4;
loop = 0;
while (loop < n)
{
	switch (v[loop * 4 + 1])
	{
	case 3
		PrintLn(loop, ")", v[loop * 4 + 1], " ", v[loop * 4 + 2], " " , v[loop *4 + 3], " ", StrFromUTF8(tmp, v[loop * 4]));
		break;
		
	case 0
		PrintLn(loop, ")", v[loop * 4 + 1], " ", v[loop * 4 + 2], " " , v[loop *4 + 3], " ", v[loop * 4]);
		break;
		
	default
		PrintLn("Unhandled object identifier type ", v[loop * 4 + 1]);
		break;
	}
	Inc(loop);
}

EndScript

Script ReadDataHelper
(host, vNode, vValue)

//if ReadMulti fail, return -1
//if ReadMulti succeed but there is U0, return -2

I4 ret;
Reference p;
:host.ReadMulti(vNode, vValue, ret);
if (ret == 0)
{
	foreach(p, vValue)
	{
		if (GetActualType(p) == "U0")
		{
			return -2;
		}
	}
}
return ret;
EndScript


Script ReadDataHelperEx
(host, vNode, vValue)

//if ReadMulti fail, return -1
//if ReadMulti succeed but there is U0, return -2, but specific point is set to 0

I4 ret;
Reference p;
:host.ReadMulti(vNode, vValue, ret);
if (ret == 0)
{
	foreach(p, vValue)
	{
		if (GetActualType(p) == "U0")
		{
			p = 0;
			ret = -2;
		}
	}
}
return ret;

EndScript

Script AggressiveScan
(host, alias, function_name, p1, p2)		//p1 is the q name (LSR_PT_A), p2 is the location name (LSR_A_OUT)
(host, alias, function_name, qname, curPos)

Object value;
String OldValue, converted;
U4 ret;
U4 Count;
U4 t0, delta;
Vector v;

v = Parameter;
VectorRemove(v, 2);		//remove function_name
VectorRemove(v, 1);		//remove alias
VectorRemove(v, 0);		//remove host

Again:
t0 = GetTick();
:host.Read(alias, value, ret);
if (ret)
{
	AppLog("[Error] ", ScriptName(), " failed to read ", alias);
}
else
{
	if (GetActualType(value) != "EOF")
	{
		converted = value;
		if (OldValue == converted)
		{
			Inc(Count);
		}
		else
		{
			OldValue = converted;
			AppLog("[", ScriptName(), "] ", host, " ", alias, " changed to ", value, ", count = ", Count);
			FCallEx(function_name, v, value);
			Count = 0;
		}		
	}
}
delta = GetTick() - t0;

if (delta < 100)
{
	Sleep(100 - delta);
}
goto Again;

EndScript


Script AggressiveScanEx
(alias, function_name, p1, p2)		//p1 is the q name (LSR_PT_A), p2 is the location name (LSR_A_OUT)
(alias, function_name, qname, curPos)

String host;
U4 ret;
U4 Count;
U4 t0, delta;
Vector v;
U4 value,OldValue,converted;
PU4 value1;

if (IMapCheck(:EAP["Omron"], alias) == 0)
{
	AppLog("[Error] Omron alias ", alias, " is not configured");
	return;
}

v = Parameter;
VectorRemove(v, 1);		//remove function_name
VectorRemove(v, 0);		//remove alias

Again:

t0 = GetTick();

if (Omron_ReadU4Array(alias,1,value1))
{
	AppLog("[Error] ", ScriptName(), " failed to read ", alias);
}
else
{
	value=value1[0];
	AppLog(ScriptName(), " Omron_ReadWord ", alias , " is successful.  value = ", value);
	converted = value;
	if (OldValue == converted)
	{
		Inc(Count);
	}
	else
	{
		OldValue = converted;
		AppLog(alias, " changed to ", value, ", count = ", Count);
		FCallEx(function_name, v, value);
		Count = 0;
	}	
}

delta = GetTick() - t0;
if (delta < 100)
{
	Sleep(100 - delta);
}
goto Again;

EndScript


Script DefaultSubsCallBack
(alias, value, host)
String ThreadName = :host.ThreadName;
ThreadPost(:ThreadName, :host.CallBack, alias, value, host);		//making call back
EndScript

Script ActiveHandShake
(host, self, other, t)

U4 t0;
I4 ret;
Object value;

:host.Write(self, 1, ret);
if (ret)
{
	AppLog("[ActiveHandShake] Failed to write 1 to ", self, " at ", host);
	return -1;
}

t0 = GetTick();
A1:
:host.Read(other, value, ret);
if (ret)
{
	AppLog("[ActiveHandShake] Failed to read 1 from ", other, " at ", host);
	:host.Write(self, 0, ret);
	if (ret)
	{
		AppLog("[ActiveHandShake] Failed to write 0 to ", self, " at ", host);
	}
	return -1;
}

if (value == 0)
{
	if (GetTick() - t0 < t)
	{
		goto A1;
	}
	AppLog("[ActiveHandShake] Timeout waiting for 1 from ", other, " at ", host);
	:host.Write(self, 0, ret);
	if (ret)
	{
		AppLog("[ActiveHandShake] Failed to write 0 to ", self, " at ", host);
	}
	return -1;
}

:host.Write(self, 0, ret);
if (ret)
{
	AppLog("[ActiveHandShake] Failed to write 0 to ", self, " at ", host);
	return -1;
}

t0 = GetTick();
A2:
:host.Read(other, value, ret);
if (ret)
{
	AppLog("[ActiveHandShake] Failed to read 0 from ", other, " at ", host);
	return -1;
}

if (value)
{
	if (GetTick() - t0 < t)
	{
		goto A2;
	}
	AppLog("[ActiveHandShake] Timeout waiting for 0 from ", other, " at ", host);
	return -1;
}

return 0;

EndScript

Script PassiveHandShake
(host, self, other, t)

U4 t0;
I4 ret;
Object value;

:host.Write(self, 1, ret);
if (ret)
{
	AppLog("[PassiveHandShake] Failed to write 1 to ", self, " at ", host);
	return -1;
}

t0 = GetTick();
A1:
:host.Read(other, value, ret);
if (ret)
{
	AppLog("[PassiveHandShake] Failed to read 0 from ", other, " at ", host);
	:host.Write(self, 0, ret);
	if (ret)
	{
		AppLog("[PassiveHandShake] Failed to write 0 to ", self, " at ", host);
	}
	return -1;
}

if (value)
{
	if (GetTick() - t0 < t)
	{
		goto A1;
	}
	AppLog("[PassiveHandShake] Timeout waiting for 0 from ", other, " at ", host);
	:host.Write(self, 0, ret);
	if (ret)
	{
		AppLog("[PassiveHandShake] Failed to write 0 to ", self, " at ", host);
	}
	return -1;
}

:host.Write(self, 0, ret);
if (ret)
{
	AppLog("[PassiveHandShake] Failed to write 0 to ", self, " at ", host);
	return -1;
}
	
return 0;

EndScript

Script TReadScheme
(host, s)

Vector v;
I4 ret;
ret = :host.ReadScheme(s, v);
if (ret)
{
	PrintLn("Failed to ReadScheme, ", ret);
	return;
}
PrintLn(v);
EndScript

Script TRead
(host, alias)
Object x;
U4 ret;

:host.Read(alias, x, ret);
if (ret)
{
	PrintLn("Failed to read ", alias, " at ", host);
}
else
{
	PrintLn("Type is ", GetActualType(x));
	PrintLn("Value is ", x);
}
EndScript

Script TRemoveMon
(host, sub, mon)
:host.DeleteMonitoredItem(sub, mon);
EndScript

Script TRemoveSub
(host, sub)
:host.DeleteSubscription(sub);
EndScript
